package com.didd.core.redislock;

import com.didd.core.common.base.RpcResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.reflect.Method;
import java.util.UUID;

/**
 * Redis切面拦截
 *
 * @author diddd
 * @Created 2020/5/15 16:18
 */
@Slf4j
@Aspect
public class RedisLockAspect {

    private RedisLockManager redisLockManager;

    public RedisLockAspect(RedisLockManager redisLockManager) {
        this.redisLockManager = redisLockManager;
    }


    /**
     * 表示在执行被@RedisLock标记的方法前后执行  aroundRedisLock()方法
     *
     * @param pjp 连接点，就是被拦截点
     * @see RedisLock
     */
    @Around(value = "@annotation(lock)")
    public Object aroundRedisLock(ProceedingJoinPoint pjp, RedisLock lock) throws Throwable {
        //获取类的字节码对象，通过字节码对象获取方法信息
        Class<?> targetCls = pjp.getTarget().getClass();
        //获取方法签名(通过此签名获取目标方法信息)
        MethodSignature ms = (MethodSignature) pjp.getSignature();
        //获取目标方法上的注解指定的操作名称
        Method targetMethod = targetCls.getDeclaredMethod(ms.getName(), ms.getParameterTypes());
        String lockType = targetCls.getName() + "#" + targetMethod.getName();
        String businessKey = getBusinessLockType(pjp, lock);
        if (StringUtils.isBlank(businessKey)) {
            businessKey = "METHOD";
        }

        // 请求ID
        String requestId = UUID.randomUUID().toString();
        RpcResponse<Void> holdLock = redisLockManager.holdLock(buildKey(lockType, businessKey), requestId, lock.retry(), lock.acquireTime(), lock.expireTime(), lock.isThrowable());
        if (!holdLock.hasSuccess()) {
            return holdLock;
        }

        Object proceedObj;
        try {
            //执行业务逻辑方法
            proceedObj = pjp.proceed();
        } finally {
            //释放锁
            if (holdLock.hasSuccess()) {
                redisLockManager.releaseLock(buildKey(lockType, businessKey), requestId, lock.isThrowable());
            }
        }
        return proceedObj;
    }

    private String getBusinessLockType(JoinPoint jp, RedisLock lock) {
        Object[] paramValues = jp.getArgs();
        String[] paramNames = ((CodeSignature) jp.getSignature()).getParameterNames();
        if (paramNames != null && paramNames.length > 0 && StringUtils.isNotBlank(lock.paramFlied())) {
            for (int i = 0; i < paramNames.length; i++) {
                Object pName = paramNames[i];
                Object pValue = paramValues[i];
                log.info("paramName:[{}];paramValue:[{}],paramType:[{}]", paramNames[i], paramValues[i], paramValues[i].getClass());
                if (pName.equals(lock.paramFlied())) {
                    return (String) pValue;
                }
                String attrValue = getFieldValueByAttrName(lock.paramFlied(), pValue);
                if (StringUtils.isNotBlank(attrValue)) {
                    return attrValue;
                }
            }
        }
        return null;
    }

    /**
     * 根据属性名获取属性值
     *
     * @param fieldName 属性名称
     * @param object    对象
     * @return
     */
    private String getFieldValueByAttrName(String fieldName, Object object) {
        try {
            String firstLetter = fieldName.substring(0, 1).toUpperCase();
            String getter = "get" + firstLetter + fieldName.substring(1);
            Method method = object.getClass().getDeclaredMethod(getter);
            method.setAccessible(Boolean.TRUE);
            Object value = method.invoke(object);
            return (String) value;
        } catch (Exception e) {
            // log.error(e.getMessage(), e);
            return null;
        }
    }

    /**
     * 获得 redis key
     *
     * @param lockType     锁类型
     * @param businessCode 业务值
     * @return
     */
    private String buildKey(String lockType, String businessCode) {
        return lockType + "&" + businessCode;
    }

}
